#include "datetime.h"
#include <math.h>
#include <iostream>

namespace biosignal {
using namespace std;
TimePoint::TimePoint() {
    time(&m_time);
    update_time(m_time, 0);
}
TimePoint::TimePoint(int year, int month, int day, int hour, int minute,
                     int second, int useconds) {
    struct tm * timeinfo = new tm;
    timeinfo->tm_year = year - 1900;
    timeinfo->tm_mon = month - 1;
    timeinfo->tm_mday = day;
    timeinfo->tm_hour = hour;
    timeinfo->tm_min = minute;
    timeinfo->tm_sec = second;
    update_time(mktime(timeinfo), useconds);
}

std::ostream& TimePoint::display(std::ostream &out) const {
    out << m_timeinfo.tm_year + 1900 << "-" << m_timeinfo.tm_mon + 1 << "-"
    << m_timeinfo.tm_mday << " "; // << " [ " << m_time << " ] ";
    out << m_timeinfo.tm_hour << ":" << m_timeinfo.tm_min << ":"
    << m_timeinfo.tm_sec << "  " << m_useconds << " uS ";
    return out;
}

TimePoint& TimePoint::operator=(const TimePoint& rhs) {
    if (this != &rhs) {
        update_time(rhs.m_time, rhs.m_useconds);
    }
    return *this;
}
/*
 * TODO += and -=   ->spacial cases for months and years
 */
TimePoint& TimePoint::operator+=(const TimeOffset& offset) {
    update_time(m_time += offset.full_seconds(), m_useconds
                += offset.microseconds());
    return *this;
}
TimePoint& TimePoint::operator-=(const TimeOffset& offset) {
    update_time(m_time -= offset.full_seconds(), m_useconds
                -= offset.microseconds());
    return *this;

}

//TODO: fix this, is very primitive
TimeOffset TimePoint::operator-(const TimePoint& other) const {
    long full_seconds = difftime(m_time, other.m_time);
    double frac_seconds = (double)((m_useconds - other.m_useconds))/1000000.0;
    double total_seconds = full_seconds + frac_seconds;
    TimeOffset result = TimeOffset::Seconds( total_seconds );
    return result;
}

TimePoint TimePoint::operator+(const TimeOffset& other) const {
    TimePoint result = *this;
    result += other;
    return result;
}
TimePoint TimePoint::operator-(const TimeOffset& other) const {
    TimePoint result = *this;
    result -= other;
    return result;

}
bool TimePoint::operator==(const TimePoint &other) const {
    return (m_time == other.m_time && m_useconds == other.m_useconds);
}
bool TimePoint::operator!=(const TimePoint &other) const {
    return !((*this) == other);
}

bool TimePoint::operator>=(const TimePoint &other) const {
    if (m_time == other.m_time) {
        return m_useconds >= other.m_useconds;
    } else {
        return m_time >= other.m_time;
    }
}
bool TimePoint::operator<(const TimePoint &other) const {
    if (m_time == other.m_time) {
        return m_useconds < other.m_useconds;
    } else {
        return m_time < other.m_time;
    }
}

void TimePoint::update_time(time_t time, int usec) {
    m_time = time;
    m_useconds = usec;
    m_timeinfo = *localtime(&m_time);
}

int TimePoint::year() const {
    return m_timeinfo.tm_year;
}
int TimePoint::month() const {
    return m_timeinfo.tm_mon;

}
int TimePoint::day() const {
    return m_timeinfo.tm_mday;
}
int TimePoint::hour() const {
    return m_timeinfo.tm_hour;
}
int TimePoint::minute() const {
    return m_timeinfo.tm_min;
}
int TimePoint::second() const {
    return m_timeinfo.tm_sec;
}

// ============================== TimeOffset ================

TimeOffset::TimeOffset(int years, int months, int days, int hours, int minutes,
                       int seconds, int microseconds) {
    m_useconds = microseconds;
    m_timeinfo.tm_sec = seconds;
    m_timeinfo.tm_min = minutes;
    m_timeinfo.tm_hour = hours;
    m_timeinfo.tm_mday = days;
    m_timeinfo.tm_mon = 0;
    m_timeinfo.tm_year = 0;
    m_timeinfo.tm_wday = 0;
    m_timeinfo.tm_yday = 0;
    m_timeinfo.tm_isdst = 0;
}

TimeOffset& TimeOffset::operator=(const TimeOffset& rhs) {
    if (this != &rhs) {
        m_timeinfo = rhs.m_timeinfo;
        m_useconds = rhs.m_useconds;
    }
    return *this;
}

bool TimeOffset::is_negative() const {
    return (total_microseconds() < 0); //TODO fix
}

//TODO : fix all this
TimeOffset& TimeOffset::operator+=(const TimeOffset& rhs) {
    m_useconds += rhs.m_useconds;
    m_timeinfo.tm_sec += rhs.m_timeinfo.tm_sec;
    m_timeinfo.tm_min += rhs.m_timeinfo.tm_min;
    m_timeinfo.tm_hour += rhs.m_timeinfo.tm_hour;
    m_timeinfo.tm_mday += rhs.m_timeinfo.tm_mday;
    m_timeinfo.tm_mon += rhs.m_timeinfo.tm_mon;
    m_timeinfo.tm_year += rhs.m_timeinfo.tm_year;
    m_timeinfo.tm_wday += rhs.m_timeinfo.tm_wday;
    m_timeinfo.tm_yday += rhs.m_timeinfo.tm_yday;
    m_timeinfo.tm_isdst += rhs.m_timeinfo.tm_isdst;
    mktime(&m_timeinfo);
    return *this;
}
TimeOffset& TimeOffset::operator-=(const TimeOffset& rhs) {
    m_useconds -= rhs.m_useconds;
    m_timeinfo.tm_sec -= rhs.m_timeinfo.tm_sec;
    m_timeinfo.tm_min -= rhs.m_timeinfo.tm_min;
    m_timeinfo.tm_hour -= rhs.m_timeinfo.tm_hour;
    m_timeinfo.tm_mday -= rhs.m_timeinfo.tm_mday;
    m_timeinfo.tm_mon -= rhs.m_timeinfo.tm_mon;
    m_timeinfo.tm_year -= rhs.m_timeinfo.tm_year;
    m_timeinfo.tm_wday -= rhs.m_timeinfo.tm_wday;
    m_timeinfo.tm_yday -= rhs.m_timeinfo.tm_yday;
    m_timeinfo.tm_isdst -= rhs.m_timeinfo.tm_isdst;
    mktime(&m_timeinfo);
    return *this;
}
TimeOffset TimeOffset::operator+(const TimeOffset& other) {
    TimeOffset result = *this;
    result+=other;
    return result;
}
TimeOffset TimeOffset::operator-(const TimeOffset& other) {
    TimeOffset result = *this;
    result-=other;
    return result;
}
TimeOffset TimeOffset::operator*(double factor) {
    double new_seconds = total_seconds() * factor;
    TimeOffset result = TimeOffset::Seconds(new_seconds);
    return result;

}

bool TimeOffset::operator==(const TimeOffset &other) const {
//	bool result = (m_timeinfo == other.m_timeinfo) && (m_useconds == other.m_useconds);
    bool result;
    return result;

}
bool TimeOffset::operator!=(const TimeOffset &other) const {
    return !((*this) == other);
}

int TimeOffset::days() const {
    return m_timeinfo.tm_mday;
}
int TimeOffset::hours() const {
    return m_timeinfo.tm_hour;
}
int TimeOffset::minutes() const {
    return m_timeinfo.tm_min;
}
int TimeOffset::seconds() const {
    return m_timeinfo.tm_sec;
}
int TimeOffset::microseconds() const {
    return m_useconds;
}

long TimeOffset::full_days() const {
    return days();
}
long TimeOffset::full_hours() const {
    return full_days() * 24 + hours();
}
long TimeOffset::full_minutes() const {
    return full_hours() * 60 + minutes();
}
long TimeOffset::full_seconds() const {
    return full_minutes() * 60 + seconds();
}
long TimeOffset::total_microseconds() const {
    long seconds = full_seconds() * 1000000;
    return  seconds + microseconds();
}
double TimeOffset::total_seconds() const {
    double ts = full_seconds() + ((double)microseconds())/1000000.0;
    return ts;
}

TimeOffset TimeOffset::Seconds(double seconds) {
    int s = seconds;
    int usec = round((seconds - s) * 1000000);

    int sdays = (s / (60 * 60 * 24));
    int wosdays = (sdays > 0 ? sdays : -sdays);
    if (wosdays > 0)
        s = s % (wosdays*(60 * 60 * 24));
    int hours = (s / (60 * 60));
    if (hours > 0)
        s = s % (hours*(60 * 60));
    int minutes = (s / (60));
    if (minutes > 0)
        s = s % (minutes*60);
    int sec = (s / 1);

    TimeOffset result(0, 0, sdays, hours, minutes, sec, usec);
    return result;
}

std::ostream& TimeOffset::display(std::ostream &out) const {
    out << days() << " - ";
    out.width(2);
    out.fill('0');
    out << hours();
    out << ":" ;
    out.width(2);
    out.fill('0');
    out << minutes();
    out << ":";
    out.width(2);
    out.fill('0');
    out << seconds();
    out << " " << microseconds() << " us ";
    return out;
}

//==============================TimePeriod==========================

TimePeriod::TimePeriod() {
}
TimePeriod::TimePeriod(TimePoint begin, TimePoint end) {
    m_begin = begin;
    m_duration = end - begin;

}
TimePeriod::TimePeriod(TimePoint begin, TimeOffset duration) {
    m_begin = begin;
    m_duration = duration;
}
TimePoint TimePeriod::begin() const {
    return m_begin;
}
TimePoint TimePeriod::end() const {
    return m_begin + m_duration;
}

TimeOffset TimePeriod::duration() const {
    return m_duration;
}

bool TimePeriod::contains(const TimePoint& time_point) const {
    return (time_point >= begin() && time_point < end());
}

bool TimePeriod::contains(const TimePeriod& time_period) const {
    return (time_period.begin() >= begin() && time_period.end() < end());
}


TimePeriod TimePeriod::insersection(const TimePeriod& tp) const {
    if (!this->insersects(tp))
        throw NotIntersectsException();
    
    TimePoint nstart, nend;
    if (contains(tp.begin())) {
        nstart = tp.begin();
    } else {
        nstart = begin();
    }
    nend = ( tp.end() < end() ) ? tp.end() : end();
    
    return TimePeriod(nstart, nend);
}

bool TimePeriod::insersects(const TimePeriod& tp) const {
    return contains(tp.begin()) || tp.contains(this->begin());
}


void TimePeriod::duration(TimeOffset duration) {
    m_duration = duration;
}
void TimePeriod::move(TimeOffset offset) {
    m_begin += offset;
}

void TimePeriod::begin(TimePoint begin) {
    m_begin = begin;
}

bool TimePeriod::operator==(const TimePeriod &other) const {
  return (m_begin==other.m_begin && m_duration==other.m_duration);
}
bool TimePeriod::operator!=(const TimePeriod &other) const {
  return !(*this==other);
}
    

std::ostream& TimePeriod::display(std::ostream &out) const {
    out << begin() << " - " << end() ;
    return out;
}


}
